/**
 * Copyright 2006 gworks.com.au
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. (http://www.apache.org/licenses/LICENSE-2.0)
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for 
 * the specific language governing permissions and limitations under the License.
 *
 * <dhamma-message src="Atisha (11th century Tibetan Buddhist master)">
 * 	The greatest achievement is selflessness. The greatest worth is self-mastery.
 * 	The greatest quality is seeking to serve others. The greatest precept is continual awareness.
 * 	The greatest medicine is the emptiness of everything. The greatest action is not conforming with the worlds ways.
 * 	The greatest magic is transmuting the passions. The greatest generosity is non-attachment.
 * 	The greatest goodness is a peaceful mind. The greatest patience is humility.
 * 	The greatest effort is not concerned with results. The greatest meditation is a mind that lets go.
 * 	The greatest wisdom is seeing through appearances. 
 * </dhamma-message>
 */
package org.javaongems.client.stdctrls;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.javaongems.client.Gem;
import org.javaongems.client.rt.io.AsyncCallbackAdapter;
import org.javaongems.client.rt.text.dom.DomExtUtils;
import org.javaongems.client.service.GemletConstants;
import org.javaongems.client.service.HttpPostRpc;
import org.javaongems.client.service.HttpPostRpcAsync;

import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Frame;

/**
 * <p>Provide a frame that sources its upload file fields from an external
 * html page.</p>
 * <p>Background:</p>
 * File uploading posses an interesting challenge due to the javascript/
 * browser security issues. Only files that have been selected by the user 
 * using the input type="file" entries can be posted. This means that it
 * is not possible to dynamically construct input file type dom objects 
 * and assign values prior to posting to the server.
 * </p>
 * <p>
 * Another problem is that the HTTP post approach is synchronous and works
 * against the rest of the GWT/Gems application. Therefore, what we need to
 * able to solve is have the post execute in a frame and simulate the 
 * async callback functionality. 
 * </p>
 * <p>Responsibilities:
 * <ul>
 * 	<li> collaborate with an external html page to construct the upload file set
 * 	<li> build a set of hidden input fields that can be dynamically assembled
 * 		as part of the multi-part post
 *  <li> submit a post that blocks and then collaborates with a Http Post Gate
 *  	Keeper action and a pickup rpc service implementation
 * 	<li> register a callback listener that will be invoked when when the rpc
 * 		service completes 
 * </ul>
 * </p>
 * <p>Collaborators:
 * <ul>
 * 	<li> server-side html form file (eg. /public/sample-upload-form.jsp)
 * 	<li> server-side HttpPostGateKeeperAction
 * 	<li> server-side rpc pick up (eg. BaseHttpPostPickupRpcImpl)    
 * </ul>
 * </p>
 * <p>Constraints:
 * <ul>
 * 	<li> Do not attempt to asynchronously run both pingProgress and 
 * 		pickupAndHandle. Such seem to cause the browser to enter into
 * 		a deadlock state.
 * 	<li> Although the server and client side code appears to support it, 
 * 		submitting two http post forms concurrently will cause the browser
 * 		to hang.
 * </ul>
 * </p> 
 * 
 * @author Ashin Wimalajeewa (ash)
 */
public class HttpPostForm extends Frame {
	static public interface ProgressListener {
		public void onProgressReported(HttpPostRpc.ProgressInfo progress);
	}
	
	final static public String FORM_ID = "gemsHttpPostForm";
	final static public String HIDDEN_INPUTS_ID = "gemsHiddenInputs";

	static private int  formCounter = 0;
	
	protected Map 				hiddenValues = new HashMap();
	protected String			action = "";
	
	protected AsyncCallback		pickupMonitor = new PickupInterceptor();
	protected AsyncCallback		progressMonitor = new ProgressMonitor();
	protected ProgressTask		progressTask = new ProgressTask();
	
	protected AsyncCallback		serviceCallback;
	protected ProgressListener	progressListner;
	private   String			gemId;
	private   String			orderNo;
	private   int				pingSleepDuration = 750;	// 3/4 of a sec	
	
	private   HttpPostRpcAsync serviceProvider;
	
	public HttpPostForm() {
		int defaultId = formCounter++;
		gemId = Gem.stone.getGemId();
		orderNo = this.hashCode() + "-" + defaultId;
		hiddenValues.put(GemletConstants.GEM_ID, gemId);
		hiddenValues.put(GemletConstants.GEM_ORDER_NO, orderNo);
		if (Gem.stone.isDebugEnabled()) Gem.stone.log("created HttpPostForm [" + gemId + "," + orderNo +"]");
	}
	
	public String getOrderNo() {
		return orderNo;
	}
	
	public Map getInputHidden() {
		return hiddenValues;
	}
	
	public String getAction() {
		return action;
	}
	
	public void setAction(String actn) {
		action = actn;
	}
	
	public void setPingSleepDuration(int dur) {
		pingSleepDuration = dur;
	}
	
	public int getPingSleepDuration() {
		return pingSleepDuration;
	}
	
	public void setAsyncCallback(AsyncCallback callback) {
		serviceCallback = callback;
	}
	
	public void setProgressListener(ProgressListener lsnr) {
		progressListner = lsnr;
	}
	
	public void submit(HttpPostRpcAsync service) {
		Element form = prepareForm();
		serviceProvider = service;
		progressTask.schedule(getPingSleepDuration());
		DomExtUtils.submitForm(form);
		if (Gem.stone.isDebugEnabled()) Gem.stone.log("http post submitted [" + gemId + "," + orderNo+ "]");
	}
	
	protected void pingProgress() {
		if (serviceProvider != null) { 
			if (Gem.stone.isDebugEnabled()) Gem.stone.log("pinging progress [" + gemId + "," + orderNo+ "]");
			serviceProvider.pingProgress(gemId, orderNo, progressMonitor);
		}
	}
	
	protected void pickupPayload() {
		if (serviceProvider != null) { 
			if (Gem.stone.isDebugEnabled()) Gem.stone.log("picking up payload [" + gemId + "," + orderNo+ "]");
			serviceProvider.pickupAndHandle(gemId, orderNo, pickupMonitor);
		}
	}

	private Element prepareForm() {
		Element frame = getElement();
		Element frm = DomExtUtils.getElementByIdFromFrame(frame, FORM_ID);
		Element hiddens = DomExtUtils.getElementByIdFromFrame(frame, HIDDEN_INPUTS_ID);
		prepareFormAction(frm);
		prepareHiddenInputs(hiddens);
		return frm;
	}
	
	private void prepareFormAction(Element form) {
		DOM.setAttribute(form, "action", getAction());
	}
	
	private void prepareHiddenInputs(Element div) {
		StringBuffer inputs = new StringBuffer();
		appendInputHiddenFields(inputs);
		String html = inputs.toString();
		DOM.setInnerHTML(div, html);
	}
	
	protected void appendInputHiddenFields(StringBuffer buff) {
		for (Iterator iter = hiddenValues.keySet().iterator(); iter.hasNext();) {
			String name = (String) iter.next();
			String value = (String) hiddenValues.get(name);
			if (value == null)
				continue;
			appendInputHiddenField(buff, name, value);
		}
	}
	
	private void appendInputHiddenField(StringBuffer buff, String name, String val) {
		appendInputField(buff, "hidden", name, val);
	}

	private void appendInputField(StringBuffer buff, String type, String name, String val) {
		StringBuffer ret = new StringBuffer("\r\n<input type=\"");
		ret.append(type).append("\" name=\"");
		ret.append(name).append("\" value=\"").append(val).append("\">");
		String field = ret.toString();
		buff.append(field);
	}
	
	protected class PickupInterceptor implements AsyncCallback {
		public void onFailure(Throwable caught) {
			if (Gem.stone.isDebugEnabled()) Gem.stone.log("pickup failed [" + gemId + "," + orderNo+ "]");
			if (serviceCallback != null)
				serviceCallback.onFailure(caught);
		}

		public void onSuccess(Object result) {
			if (Gem.stone.isDebugEnabled()) Gem.stone.log("pickup successfull [" + gemId + "," + orderNo+ "]");
			if (serviceCallback != null)
				serviceCallback.onSuccess(result);
		}
	}
	
	protected class ProgressMonitor extends AsyncCallbackAdapter {
		public void onSuccess(Object result) {
			HttpPostRpc.ProgressInfo progress = (HttpPostRpc.ProgressInfo) result;
			if (Gem.stone.isDebugEnabled()) {
				String log = "monitor: [" + gemId + "," + orderNo+ "] " + 
							(progress == null ? "null": progress.toString()); 
				Gem.stone.log(log);
			}
			if (progressListner != null) 
				progressListner.onProgressReported(progress);
			if (progress != null) {
				if (progress.currentPhase == HttpPostRpc.ProgressInfo.UPLOADING_PHASE) 
					progressTask.schedule(getPingSleepDuration());
				else if (progress.currentPhase == HttpPostRpc.ProgressInfo.PROCESSING_PHASE)
					pickupPayload();
			}
		}
	}
	
	protected class ProgressTask extends Timer {
		public void run() {
			pingProgress();
		}
	}
}